﻿using Micro.Extensions.Utilities;
using Micro.Srv.LogCenter.Services.Interfaces;
using Microsoft.Extensions.Options;
using Nest;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;

namespace Micro.Srv.LogCenter.Services
{
    public class LogService : ILogService
    {
        private readonly ElasticClient _client;
        private readonly IOptions<ElasticSearchOptions> _options;

        public LogService(ElasticClientHelper elasticClientHelper, IOptions<ElasticSearchOptions> options)
        {
            _client = elasticClientHelper.ElasticClient;
            _options = options;
        }

        public async Task<ComResult<MessageViewModel[]>> GetLogsAsync(LogSearchCondition condition, int pageIndex, int pageSize)
        {
            try
            {
                var index = ESIndexHelper.GetLogIndex(_options.Value.DefaultLogPrefix, condition.LogBeginTime, condition.LogEndTime);
                var query = BuildQueryContainer(condition);
                var response = await _client.SearchAsync<MessageViewModel>(s => s
                    .Index(index)
                    .From(pageIndex * pageSize)
                    .TrackTotalHits(true)
                    .Size(pageSize)
                    .Query(q => query)
                    .Sort(st => st.Descending(d => d.LogTime)));

                if (response.ApiCall.Success)
                {
                    var result = response.Hits.Select(s =>
                    {
                        s.Source.Id = s.Id;
                        return s.Source;
                    }).ToArray();
                    return ComResult.Success(result, new AttachPage(pageIndex,pageSize,response.Total));
                }

                response.TryGetServerErrorReason(out string reason);
                return ComResult.Failed<MessageViewModel[]>(400, reason);
            }
            catch (Exception ex)
            {
                return ComResult.Failed<MessageViewModel[]>(400, ex.ToString());
            }
        }

        public async Task<ComResult<MessageViewModel>> GetLogByObjectIdAsync(string id, DateTime beginTime, DateTime endTime)
        {
            var index = ESIndexHelper.GetLogIndex(_options.Value.DefaultLogPrefix, beginTime, endTime);

            var response = await _client.SearchAsync<MessageViewModel>(s => s
               .Index(index)
               //.Type("doc")
               .Query(q => q.MatchPhrase(m => m.Field("_id").Query(id))));

            if (response.ApiCall.Success)
            {
                var result = response.Hits.Select(s =>
                {
                    return s.Source;
                }).FirstOrDefault();
                return ComResult.Success<MessageViewModel>(result);
            }

            response.TryGetServerErrorReason(out string reason);
            return ComResult.Failed<MessageViewModel>(400, reason);
        }

        #region private method
        private QueryContainer BuildQueryContainer(LogSearchCondition condition)
        {
            var queryCombin = new List<Func<QueryContainerDescriptor<MessageViewModel>, QueryContainer>>();
            if (!string.IsNullOrEmpty(condition.ApplicationId))
                queryCombin.Add(mt => mt.Match(m => m.Field(t => t.ApplicationId).Query(condition.ApplicationId))); //字符串匹配

            if (!string.IsNullOrEmpty(condition.ChainId))
                queryCombin.Add(mt => mt.MatchPhrase(m => m.Field(t => t.ChainId).Query(condition.ChainId))); //字符串匹配

            if (!string.IsNullOrEmpty(condition.HostIPAddress))
                queryCombin.Add(mt => mt.Match(m => m.Field(t => t.HostIPAddress).Query(condition.HostIPAddress)));

            if (condition.LogLevel.HasValue)
                queryCombin.Add(mt => mt.Term(m => m.Field(t => t.LogLevel).Value(GetLogLevelString(condition.LogLevel.Value))));

            if (!string.IsNullOrEmpty(condition.LogType))
                queryCombin.Add(mt => mt.MatchPhrase(m => m.Field(t => t.LogType).Query(condition.LogType)));

            if (!string.IsNullOrEmpty(condition.Message))
                queryCombin.Add(mt => mt.MatchPhrase(m => m.Field(t => t.Message).Query(condition.Message)));

            if (condition.TimeSpan.HasValue)
                queryCombin.Add(mt => mt.Range(m => m.Field(t => t.TimeSpan).GreaterThanOrEquals(condition.TimeSpan.Value)));

            if (!string.IsNullOrEmpty(condition.RequestPath))
                queryCombin.Add(mt => mt.MatchPhrase(m => m.Field(t => t.Request.Path).Query(condition.RequestPath)));

            if (!string.IsNullOrEmpty(condition.HttpStatusCode))
                queryCombin.Add(mt => mt.MatchPhrase(m => m.Field(t => t.Response.StatusCode).Query(condition.HttpStatusCode)));

            if (!string.IsNullOrEmpty(condition.ResponseBody))
                queryCombin.Add(mt => mt.MatchPhrase(m => m.Field(t => t.Response.Body).Query(condition.ResponseBody)));

            if (!string.IsNullOrEmpty(condition.RequestBody))
                queryCombin.Add(mt => mt.MatchPhrase(m => m.Field(t => t.Request.Body).Query(condition.RequestBody)));

            return Query<MessageViewModel>.Bool(b => b
               .Must(queryCombin)
               .Filter(f => f
                  .DateRange(dr => dr.Field(t => t.LogTime)
                     .GreaterThanOrEquals(DateMath.Anchored(condition.LogBeginTime.ToString("o")))
                     .LessThanOrEquals(DateMath.Anchored(condition.LogEndTime.ToString("o"))))));
        }

        private static string GetLogLevelString(LogLevel logLevel)
        {
            switch (logLevel)
            {
                case LogLevel.Trace:
                    return "trce";
                case LogLevel.Debug:
                    return "dbug";
                case LogLevel.Information:
                    return "info";
                case LogLevel.Warning:
                    return "warn";
                case LogLevel.Error:
                    return "fail";
                case LogLevel.Critical:
                    return "crit";
                default:
                    throw new ArgumentOutOfRangeException(nameof(logLevel));
            }
        }


        #endregion
    }
}
